/*
 *  Queen Mary University of London MOM System
 *  
 *  Copyright Queen Mary University of London
 *  
 *  Authors:
 *  Bob Chew (bob.chew@elec.qmul.ac.uk)
 *  Beatriz Viñal Murciano (beavimu@gmail.com)
 *  John Bigham (john.bigham@elec.qmul.ac.uk)
 *  
 *  
 *  Portions of this software uses Apache Qpid
 *    http://qpid.apache.org
 *  
 *  Apache Qpid license:
 * --------------------------------------------------------------
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * -------------------------------------------------------------
 * 
 */
package users;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.apache.qpid.client.message.JMSTextMessage;

/**
 * Listener to messages from other clients.
 */
public class SubscriberMessageListener implements MessageListener {

    /** Subscriber to which this message listener belongs. */
    private Subscriber sub;

    /**
     * Getter for the variable 'sub'.
     * @return variable 'sub'
     */
    public Subscriber getSub() {
        return sub;
    }

    /** Number of messages to keep to detect duplicates. */
    private static final int NUM_MSGS_DUP = 10;
    /** List of the IDs of the latest received messages. */
    private String[] latestMsgs = new String[NUM_MSGS_DUP];
    /** Position in the array to store the ID of the next received message. */
    private int position = 0;
    /** Indicator of whether the last message was a duplicate. */
    private boolean lastMsgDup = false;

    /**
     * Getter for the variable 'latestMsgs'.
     * @return variable 'latestMsgs'
     */
    public String[] getLatestMsgs() {
        return latestMsgs;
    }
    /**
     * Setter for the variable 'latestMsgs'.
     * @param latestMsgs  new value of the variable 'latestMsgs'
     */
    public void setLatestMsgs(final String[] latestMsgs) {
        this.latestMsgs = latestMsgs;
    }
    /**
     * Getter for the variable 'position'.
     * @return variable 'position'
     */
    public int getPosition() {
        return position;
    }
    /**
     * Setter for the variable 'position'.
     * @param position  new value of the variable 'position'
     */
    public void setPosition(final int position) {
        this.position = position;
    }
    /**
     * Getter for the variable 'lastMsgDup'.
     * @return variable 'lastMsgDup'
     */
    public boolean isLastMsgDup() {
        return lastMsgDup;
    }
    /**
     * Setter for the variable 'lastMsgDup'.
     * @param lastMsgDup  new value of the variable 'lastMsgDup'
     */
    public void setLastMsgDup(final boolean lastMsgDup) {
        this.lastMsgDup = lastMsgDup;
    }


    /**
     * Constructor.
     *
     * @param sub  sub to which this message listener belongs
     */
    public SubscriberMessageListener(final Subscriber sub) {
        this.sub = sub;
    }

    /**
     * Add the correlation ID of a message to the list of the correlation IDs
     * of the latest received messages, which will allow duplicate message
     * detection.
     *
     * @param msgCorrID  correlation ID of the message
     */
    private void addMsgCorrIDToDupList(final String msgCorrID) {
        getLatestMsgs()[getPosition()] = msgCorrID;
        setPosition((getPosition() + 1) % NUM_MSGS_DUP);
    }

    /**
     * Check if a message's correlation ID is in the list of the latest
     * received messages. If it is, the message is a duplicate.
     *
     * @param msgCorrID  correlation ID of the message to check
     * @return  true if the message is a duplicate of one of the NUM_MSGS_DUP
     *                  latest received messages
     *          false otherwise
     */
    private boolean isMessageDuplicate(final String msgCorrID) {
        for (String corrID : getLatestMsgs()) {
            if (msgCorrID.equals(corrID)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get the mirror subscriber which should read a message.
     *
     * @param msg  message received
     * @return  mirror subscriber which should read a message or null if it
     *          can't be found
     */
    private MirrorSubscriber getMirrorSubFor(final JMSTextMessage msg) {
        try {
            //get topic
            String dest = msg.getJMSDestination().toString();
            int end = dest.indexOf("/?routingkey");
            int ini = dest.lastIndexOf('/', end - 1) + 1;
            String topicName = dest.substring(ini, end);
            //get subscription
            Subscription sub = getSub().getSubscription(topicName);
            //get mirror subscriber
            return getSub().getSubsMirrors().get(sub);
        } catch (JMSException e) {
            System.err.println("Can't find the correct mirror subscriber");
        }
        return null;
    }

    /**
     * This method is asynchronously executed when a message from an
     * application client is received.
     *
     * @param msg  received message
     */
    @Override
    public void onMessage(final Message msg) {

//        for (int i = 0; i < 1000000; i++) { //TODO
//            for (int j = 0; j < 100; j++) {
//                //wait
//            }
//        }

        try {

            JMSTextMessage message = (JMSTextMessage) msg;

            //correlation ID of the message
            String msgCorrID = message.getJMSCorrelationID();

            //if message is not a duplicate
            if (!isMessageDuplicate(msgCorrID)) {

                setLastMsgDup(false);

                //tell the correct mirror subscriber to read a message
                MirrorSubscriber ms = getMirrorSubFor(message);
                if (ms != null) {
                    ms.addMsgToRead(msg.getJMSTimestamp());
                }
                //TODO: what if there isn't a mirror subscriber?
                //add ID of message to the list of latest received messages
                addMsgCorrIDToDupList(msgCorrID);

                System.out.println("Received message \"" + message.getText()
                        + "\" from " + message.getJMSDestination());

                //TODO: implement at application level what to do with the
                //message

            } else {
                setLastMsgDup(true);

                //if message is a duplicate don't do anything else
                System.out.println("Received duplicate message \""
                                   + message.getText() + "\" from "
                                   + message.getJMSDestination());
            }

        } catch (JMSException e) {
            e.printStackTrace();
        }

    }

}
